Subsurface Scattering in Ray Tracing Pipeline of UE4

Ray Tracing管线与Rasterization管线的差异

两种管线SSS实现的主要差异在于ScreenShadowMaskTexture的渲染方式不同,在Ray Tracing管线中,使用RayTracingShadowMaskTexture的内容写入到ScreenShadowMaskTexture中,具体语句在LightRendering.cpp 中的FDeferredShadingSceneRenderer::RenderLights()中:

GraphBuilder.QueueTextureExtraction(RayTracingShadowMaskTexture, &ScreenShadowMaskTexture);

追踪RayTracingShadowMaskTexture,发现其在RenderRayTracingShadows()中渲染。在RayTracingShadows.cpp中找到FDeferredShadingSceneRenderer::RenderRayTracingShadows(),可以在其中找到使用的shader类为FOcclusionRGS,进一步找到绑定的shader文件为RayTracingOcclusionRGS.usf。且RWOcclusionMaskUAV为最终传出的RayTracingShadowMaskTexture

IMPLEMENT_GLOBAL_SHADER(FOcclusionRGS, "/Engine/Private/RayTracing/RayTracingOcclusionRGS.usf", "OcclusionRGS", SF_RayGen);
...
PassParameters->RWOcclusionMaskUAV = OutShadowMaskUAV;

RayTracingOcclusionRGS.usf中,RWOcclusionMaskUAV的写入分为3种分支,其中不做降噪的分支如下,关于降噪分支会在后文提及:

const float ShadowFadeFraction = 1;
float SSSTransmission = Occlusion.TransmissionDistance;
float FadedShadow = lerp(1.0f, Square(Shadow), ShadowFadeFraction);
float FadedSSSShadow = lerp(1.0f, Square(SSSTransmission), ShadowFadeFraction);

float4 OutColor = EncodeLightAttenuation(half4(FadedShadow, FadedSSSShadow, FadedShadow, FadedSSSShadow));
RWOcclusionMaskUAV[PixelCoord] = OutColor;

而在UE4.25版本中,TransmissionDistance直接被写为Visibility,而Visibility则是阴影项Shadow的计算结果。这说明光追管线中没有计算SSS着色模型的次表面阴影项,而是暂时使用阴影项来代替,这直接导致了次表面散射效果的缺失。

Out.TransmissionDistance = (LocalSamplesPerPixel > 0) ? Out.Visibility / LocalSamplesPerPixel : Out.Visibility;

Ray Tracing管线的修复

对于基于厚度的SSS算法,Ray Tracing管线相对于Rasterization管线的优势在于可以更加精确的计算出物体上一个像素被自身遮挡的厚度以及光线到达物体表面的辐射量,我们将其分别定义为Thickness和Transmittance,于是我们就着手计算。

// The thickness of itself
// "Thickness = -1" stand for it is not shielded by itself, but shielded by others;
// "Thickness = 0" stand for it is on the surface exposed to light directly, not shielded by anything.
float Thickness = 0;
// Transmittance of the object, in the range of 0-1, is calculated with opacity of each object.
// When "Transmittance = 0" light will be completely blocked.
float Transmittance = 1;

画一张简单的示意图,以便覆盖所有可能的情况:

img

可以发现,我们需要拿到的数据有StartingPointIDClosestHitIDOpacityHitT等。


扩展Payload

UE4中定义了几种Payload,但是其中其实用到的只有FMaterialClosestHitPayloadFPackedMaterialClosestHitPayload,两者均继承自最基本的FMinimalPayload,后者是前者的打包压缩版,目的是节约Payload的体积,增强光追的性能。FMaterialClosestHitPayload的主要内容如下:

										// Unpacked  Packed
// offset bytes
// float FMinimalPayload::HitT // 0 4 32bits
#if USE_RAYTRACED_TEXTURE_RAYCONE_LOD
FRayCone RayCone; // 4 8 64bits
#endif // USE_RAYTRACED_TEXTURE_RAYCONE_LOD
float3 Radiance; // 12 6 48bits
float3 WorldNormal; // 24 6 48bits
float3 BaseColor; // 36 6 48bits
float3 DiffuseColor; // 48 0 (derived)
float3 SpecularColor; // 60 0 (derived)
float Opacity; // 72 2 16bits
float Metallic; // 76 1 8bits
float Specular; // 80 1 8bits
float Roughness; // 84 2 16bits
float Ior; // 88 2 16bits
uint ShadingModelID; // 92 2 4bits
uint BlendingMode; // 96 0 4bits (packed with ShadingModelID)
uint PrimitiveLightingChannelMask; // 100 0 3bits (packed with ShadingModelID)
float4 CustomData; // 104 4 32bits
float GBufferAO; // 120 0 (removed)
float3 IndirectIrradiance; // 124 0 48bits -- gbuffer only has float payload and there are truncation HLSL warnings
float3 WorldPos; // 136 0 (derived)
uint Flags; // 148 0 5bits (packed with ShadingModelID)
float3 WorldTangent; // 152 6 48bits
float Anisotropy; // 164 2 16bits (packed with WorldTangent)
// 168 total

其中并没有InstanceID,于是我们需要扩展这个payload,如下:

#ifdef USE_INSTANCE_ID
uint InstanceID; // 32bits
#endif

同时也需要扩展FPackedMaterialClosestHitPayload以及打包与解包的函数。

当这些做完之后可能会发现,编译后UE4会出现不明原因Crash,而且再也打不开了,这是为什么呢?

原因在于UE4其实在C++中指定了这个Payload的最大Size,当超过这个Size时,编译Shader,UE4就会直接Crash。

此时在Engine/Source/Runtime/Renderer/Private/RayTracing文件夹下的RayTracingAmbientOcclusion.cppRayTracingMaterialHitShaders.cppRayTracingShadows.cppRaytracingSkylight.cpp这四个文件中将// sizeof(FPackedMaterialClosestHitPayload)注释处的Initializer.MaxPayloadSizeInBytes修改为所需的新大小,即可解决这一问题。

可以发现的很重要的一点是,UE4为不同需求使用了不同的RayGenerationShader(RayTracingDebugMainRGS/OcclusionRGS/RayTracingPrimaryRaysRGS),但是对于不同材质其实都使用的是同一个ClosestHitShader,即MaterialCHS,同时也都使用同一个Payload,即FPackedMaterialClosestHitPayload


在UE4 Ray Tracing Debug View中显示InstanceID

为了验证UE4中每个物体的InstanceID不同,同时方便调试,我们先将InstanceID加入Ray Tracing Debug View。

首先在RayTracingDebug.usf添加一个case,根据InstanceID的值显示颜色:

case RAY_TRACING_DEBUG_VIZ_INSTANCE_ID:
Result = float4((Payload.InstanceID % 10) / 10.0f, (Payload.InstanceID % 5) / 5.0f, (Payload.InstanceID % 3) / 3.0f, 1.0f);
break;

同时在Shaders/Shared/RaytracingDebugDefinitions.h中需要定义RAY_TRACING_DEBUG_VIZ_INSTANCE_ID

引擎中需要修改的文件有:

Runtime/Renderer/Private/RayTracing/RayTracingDebug.cppEditor/UnrealEd/Private/RayTracingDebugVisualizationMenuCommands.cpp

此时重新编译UE4,就可以从Ray Tracing Debug View中选择InstanceID,显示效果如下:

img


关于降噪


厚度计算流程

image-20211020180843810